Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
50.00% covered (danger)
50.00%
2 / 4
CRAP
90.24% covered (success)
90.24%
37 / 41
NotGrantedValuesMerger
0.00% covered (danger)
0.00%
0 / 1
50.00% covered (danger)
50.00%
2 / 4
22.45
90.24% covered (success)
90.24%
37 / 41
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
5 / 5
 merge
0.00% covered (danger)
0.00%
0 / 1
12.28
87.50% covered (warning)
87.50%
21 / 24
 getNotGrantedValuesLocalizable
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
8 / 8
 isGrantedAttribute
0.00% covered (danger)
0.00%
0 / 1
3.14
75.00% covered (warning)
75.00%
3 / 4
<?php
declare(strict_types=1);
/*
 * This file is part of the Akeneo PIM Enterprise Edition.
 *
 * (c) 2017 Akeneo SAS (http://www.akeneo.com)
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Akeneo\Pim\Permission\Component\Merger;
use Akeneo\Pim\Enrichment\Component\Product\Factory\ValueCollectionFactoryInterface;
use Akeneo\Pim\Enrichment\Component\Product\Model\EntityWithFamilyVariantInterface;
use Akeneo\Pim\Enrichment\Component\Product\Model\EntityWithValuesInterface;
use Akeneo\Pim\Permission\Component\Attributes;
use Akeneo\Pim\Permission\Component\NotGrantedDataMergerInterface;
use Akeneo\Tool\Component\StorageUtils\Exception\InvalidObjectException;
use Akeneo\Tool\Component\StorageUtils\Repository\IdentifiableObjectRepositoryInterface;
use Doctrine\Common\Util\ClassUtils;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
/**
 * Merge not granted values with new values. Example:
 * In database, your product "my_product" contains those values:
 * {
 *    "values": {
 *      "a_text": [
 *          { "data": "my text", "locale": null, "scope": null }
 *      ],
 *      "a_number": [
 *          { "data": 12, "locale": null, "scope": null }
 *      ],
 *      "a_localizable_text": [
 *          { "data": "my text", "locale": "en_US", "scope": null },
 *          { "data": "mon text", "locale": "fr_FR", "scope": null }
 *      ]
 *    }
 * }
 *
 * But "a_text" belongs to an attribute group not viewable and locale "fr_FR" is not viewable by the connected user.
 * That's means when he will get the product "my_product", the application will return:
 * {
 *    "values": {
 *      "a_number": [
 *          { "data": 12, "locale": null, "scope": null }
 *      ],
 *      "a_localizable_text": [
 *          { "data": "my text", "locale": "en_US", "scope": null }
 *      ]
 *    }
 * }
 * (@see \Akeneo\Pim\Permission\Component\Factory\ValueCollectionFactory)
 *
 * When user will update "my_product":
 * {
 *    "values": {
 *      "a_localizable_text": [
 *          { "data": "my english text", "locale": "en_US", "scope": null }
 *      ]
 *    }
 * }
 *
 * We have to merge not granted data (here "a_text" value and "a_localizable_text" with locale "fr_FR") before saving data in database.
 * Finally, "my_product" will contain:
 * {
 *    "values": {
 *      "a_text": [
 *          { "data": "my text", "locale": null, "scope": null }
 *      ],
 *      "a_number": [
 *          { "data": 12, "locale": null, "scope": null }
 *      ],
 *      "a_localizable_text": [
 *          { "data": "my english text", "locale": "en_US", "scope": null },
 *          { "data": "mon text", "locale": "fr_FR", "scope": null }
 *      ]
 *    }
 * }
 *
 * @author Marie Bochu <marie.bochu@akeneo.com>
 */
class NotGrantedValuesMerger implements NotGrantedDataMergerInterface
{
    /** @var AuthorizationCheckerInterface */
    private $authorizationChecker;
    /** @var IdentifiableObjectRepositoryInterface */
    private $attributeRepository;
    /** @var IdentifiableObjectRepositoryInterface */
    private $localeRepository;
    /** @var ValueCollectionFactoryInterface */
    private $valueCollectionFactory;
    /**
     * @param AuthorizationCheckerInterface         $authorizationChecker
     * @param IdentifiableObjectRepositoryInterface $attributeRepository
     * @param IdentifiableObjectRepositoryInterface $localeRepository
     * @param ValueCollectionFactoryInterface       $valueCollectionFactory
     */
    public function __construct(
        AuthorizationCheckerInterface $authorizationChecker,
        IdentifiableObjectRepositoryInterface $attributeRepository,
        IdentifiableObjectRepositoryInterface $localeRepository,
        ValueCollectionFactoryInterface $valueCollectionFactory
    ) {
        $this->authorizationChecker = $authorizationChecker;
        $this->attributeRepository = $attributeRepository;
        $this->localeRepository = $localeRepository;
        $this->valueCollectionFactory = $valueCollectionFactory;
    }
    /**
     * {@inheritdoc}
     */
    public function merge($filteredEntityWithValues, $fullEntityWithValues = null)
    {
        if (!$filteredEntityWithValues instanceof EntityWithValuesInterface) {
            throw InvalidObjectException::objectExpected(ClassUtils::getClass($filteredEntityWithValues), EntityWithValuesInterface::class);
        }
        if (null === $fullEntityWithValues) {
            return $filteredEntityWithValues;
        }
        if (!$fullEntityWithValues instanceof EntityWithValuesInterface) {
            throw InvalidObjectException::objectExpected(ClassUtils::getClass($fullEntityWithValues), EntityWithValuesInterface::class);
        }
        $rawValuesToMerge = [];
        foreach ($fullEntityWithValues->getRawValues() as $attributeCode => $values) {
            $isGrantedAttribute = $this->isGrantedAttribute($attributeCode);
            if (null !== $isGrantedAttribute && false === $isGrantedAttribute) {
                $rawValuesToMerge[$attributeCode] = $values;
            } else {
                $notGrantedValuesLocalizable = $this->getNotGrantedValuesLocalizable($values);
                if (!empty($notGrantedValuesLocalizable)) {
                    $rawValuesToMerge[$attributeCode] = $notGrantedValuesLocalizable;
                }
            }
        }
        if ($filteredEntityWithValues instanceof EntityWithFamilyVariantInterface &&
            null !== $filteredEntityWithValues->getFamilyVariant()
        ) {
            $values = clone $filteredEntityWithValues->getValuesForVariation();
        } else {
            $values = clone $filteredEntityWithValues->getValues();
        }
        $fullEntityWithValues->setValues($values);
        if (!empty($rawValuesToMerge)) {
            $notGrantedValues = $this->valueCollectionFactory->createFromStorageFormat($rawValuesToMerge);
            foreach ($notGrantedValues as $notGrantedValue) {
                $fullEntityWithValues->addValue($notGrantedValue);
            }
        }
        return $fullEntityWithValues;
    }
    /**
     * @param array $values
     *
     * @return array
     */
    private function getNotGrantedValuesLocalizable(array $values): array
    {
        $notGrantedValues = [];
        foreach ($values as $channelCode => $localeRawValue) {
            foreach ($localeRawValue as $localeCode => $data) {
                if ('<all_locales>' !== $localeCode) {
                    $locale = $this->localeRepository->findOneByIdentifier($localeCode);
                    if (null !== $locale && !$this->authorizationChecker->isGranted(Attributes::VIEW_ITEMS, $locale)) {
                        $notGrantedValues[$channelCode][$localeCode] = $data;
                    }
                }
            }
        }
        return $notGrantedValues;
    }
    /**
     * @param mixed $attributeCode
     *
     * @return bool|null
     */
    private function isGrantedAttribute($attributeCode): ?bool
    {
        $attribute = $this->attributeRepository->findOneByIdentifier($attributeCode);
        if (null === $attribute) {
            return null;
        }
        return $this->authorizationChecker->isGranted(Attributes::VIEW_ATTRIBUTES, $attribute);
    }
}